package com.shijimo.file.controller.admin;

import com.shijimo.server.dto.FileDto;
import com.shijimo.server.dto.ResponseDto;
import com.shijimo.server.enums.FileUseEnum;
import com.shijimo.server.service.FileService;
import com.shijimo.server.util.Base64ToMultipartFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;

/**
 * @author Dream_飞翔
 * @date 2021/10/3
 * @time 17:19
 * @email 1072876976@qq.com
 */

@RestController
@RequestMapping("/admin")
public class UploadController {
    private static final Logger LOG = LoggerFactory.getLogger(UploadController.class);
    public static final String BUSINESS_NAME = "文件上传";

    @Value("${file.domain}")
    private String FILE_DOMAIN;

    @Value("${file.path}")
    private String FILE_PATH;

    @Resource
    private FileService fileService;

    @RequestMapping("/upload")
    public ResponseDto upload(@RequestBody FileDto fileDto) throws IOException, InterruptedException {
        LOG.info("开始上传文件：");

        String use = fileDto.getUse();
        String key = fileDto.getKey();
        String suffix = fileDto.getSuffix();
        String shardBase64 = fileDto.getShard();
        // 利用工具类将Base64编码的文件转为MultipartFile类
        MultipartFile shard = Base64ToMultipartFile.base64ToMultipart(shardBase64);

        // 将文件保存到本地
        // 通过use得到文件类型
        FileUseEnum useEnum = FileUseEnum.getByCode(use);

        // 如果文件夹不存在则创建
        String dir = useEnum.name().toLowerCase();
        File fullDir = new File(FILE_PATH + dir);
        if (!fullDir.exists()) {
            fullDir.mkdir();
        }

        // 定义返回到前端和在数据库中进行存储的路径
        String path = new StringBuffer(dir)
                .append(File.separator)
                .append(key)
                .append(".")
                .append(suffix)
                .toString();

        // 定义本地存储路径
        String localPath = new StringBuffer(path)
                .append(".")
                .append(fileDto.getShardIndex())
                .toString();

        // 定义文件的存储路径，在本地进行存储的文件是加索引号的
        String fullPath = FILE_PATH + localPath;
        File dest = new File(fullPath);

        // 将分片文件进行转存
        shard.transferTo(dest);
        // 将文件的全路径输出到日志中
        LOG.info(dest.getAbsolutePath());

        LOG.info("保存文件记录开始");
        // 保存到数据库中的文件是不加索引号的
        fileDto.setPath(path);
        fileService.save(fileDto);

        ResponseDto responseDto = new ResponseDto();
        // 返回到前端的文件仍然是不加索引号的
        fileDto.setPath(FILE_DOMAIN + path);
        responseDto.setContent(fileDto);

        // 判断是否为最后一个分片
        if (fileDto.getShardIndex().equals(fileDto.getShardTotal())) {
            this.merge(fileDto);
        }
        return responseDto;
    }

    /**
     * 将两个分片合并为一个完整的文件并将多余的分片进行删除
     */
    public void merge(FileDto fileDto) throws InterruptedException, IOException {
        LOG.info("合并分片开始");
        // 这里取到的path是全路径，进行替换处理
        String path = fileDto.getPath().replace(FILE_DOMAIN, "");
        Integer shardTotal = fileDto.getShardTotal();
        File newFile = new File(FILE_PATH + path);
        // 文件追加写入
        FileOutputStream outputStream = new FileOutputStream(newFile, true);
        // 分片文件
        FileInputStream fileInputStream = null;
        byte[] bytes = new byte[10 * 1024 * 1024];
        int length;

        try {
            for (int i = 0; i < shardTotal; i++) {
                // 读取分片
                fileInputStream = new FileInputStream(FILE_PATH + path + "." + (i + 1));
                while ((length = fileInputStream.read(bytes)) != -1) {
                    outputStream.write(bytes, 0, length);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 将输入流进行关闭
            if (fileInputStream != null) {
                fileInputStream.close();
            }
            // 关闭输出流
            outputStream.close();
            LOG.info("IO流关闭");
            LOG.info("合并分片结束");
        }

        // 告诉虚拟机进行一次垃圾回收，释放对文件的占用
        System.gc();
        // 让线程休息100毫秒，如果不让线程进行停止，有些分片仍然在上传中时就进行删除，会导致分片删除失败
        Thread.sleep(100);

        LOG.info("删除分片开始");

        for (int i = 0; i < shardTotal; i++) {
            String filePath = FILE_PATH + path + "." + (i + 1);
            File file = new File(filePath);
            boolean result = file.delete();
            LOG.info("删除{}，{}", filePath, result ? "成功" : "失败");
        }

        LOG.info("删除分片结束");
    }

    /**
     * 根据文件标识去检查上传的分片
     *
     * @param key
     * @return
     */
    @GetMapping("/check/{key}")
    public ResponseDto check(@PathVariable String key) {
        LOG.info("开始检查上传分片：{}", key);
        ResponseDto responseDto = new ResponseDto();
        FileDto fileDto = fileService.findByKey(key);
        // 如果数据库中查到了已经上传的文件记录
        if (fileDto != null) {
            // 将fileDto的全路径进行返回
            fileDto.setPath(FILE_DOMAIN + fileDto.getPath());
        }
        responseDto.setContent(fileDto);
        return responseDto;
    }

}
